Skip to contentMethod: setIndirectCollectionIfPresent(Object, Field)
1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.EntityNotFoundException;
19: import cz.cvut.kbss.jopa.exceptions.OWLEntityExistsException;
20: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
21: import cz.cvut.kbss.jopa.model.*;
22: import cz.cvut.kbss.jopa.model.EntityManagerImpl.State;
23: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
24: import cz.cvut.kbss.jopa.model.lifecycle.PostLoadInvoker;
25: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
26: import cz.cvut.kbss.jopa.model.metamodel.EntityTypeImpl;
27: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
28: import cz.cvut.kbss.jopa.query.NamedQueryManager;
29: import cz.cvut.kbss.jopa.query.ResultSetMappingManager;
30: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
31: import cz.cvut.kbss.jopa.sessions.change.ChangeManagerImpl;
32: import cz.cvut.kbss.jopa.sessions.change.ChangeRecordImpl;
33: import cz.cvut.kbss.jopa.sessions.change.ChangeSetFactory;
34: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
35: import cz.cvut.kbss.jopa.utils.CollectionFactory;
36: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
37: import cz.cvut.kbss.jopa.utils.ErrorUtils;
38: import cz.cvut.kbss.jopa.utils.Wrapper;
39: import org.aspectj.lang.Aspects;
40:
41: import java.lang.reflect.Field;
42: import java.net.URI;
43: import java.util.*;
44: import java.util.Map.Entry;
45: import java.util.function.Consumer;
46:
47: public class UnitOfWorkImpl extends AbstractSession implements UnitOfWork, QueryFactory, ConfigurationHolder, Wrapper {
48:
49: private final Set<Object> cloneMapping;
50: private final Map<Object, Object> cloneToOriginals;
51: private final Map<Object, Object> keysToClones = new HashMap<>();
52: private Map<Object, Object> deletedObjects;
53: private Map<Object, Object> newObjectsCloneToOriginal;
54: private Map<Object, Object> newObjectsOriginalToClone;
55: private final Map<Object, Object> newObjectsKeyToClone = new HashMap<>();
56: private RepositoryMap repoMap;
57:
58: private boolean hasChanges;
59: private boolean hasNew;
60: private boolean hasDeleted;
61: private boolean shouldReleaseAfterCommit;
62: private boolean shouldClearCacheAfterCommit;
63: private boolean useTransactionalOntology;
64:
65: private boolean isActive;
66: private boolean inCommit = false;
67:
68: private UnitOfWorkChangeSet uowChangeSet = ChangeSetFactory.createUoWChangeSet();
69:
70: private AbstractSession parent;
71: private AbstractEntityManager entityManager;
72: private final ConnectionWrapper storage;
73:
74: private final MergeManager mergeManager;
75: private final CloneBuilder cloneBuilder;
76: private final ChangeManager changeManager;
77: private final SparqlQueryFactory queryFactory;
78: private final CollectionFactory collectionFactory;
79: /**
80: * This is a shortcut for the second level cache.
81: */
82: private final CacheManager cacheManager;
83:
84: public UnitOfWorkImpl(AbstractSession parent) {
85: super(parent.getConfiguration());
86: this.parent = Objects.requireNonNull(parent);
87: this.cloneToOriginals = createMap();
88: this.cloneMapping = cloneToOriginals.keySet();
89: this.repoMap = new RepositoryMap();
90: repoMap.initDescriptors();
91: this.cloneBuilder = new CloneBuilderImpl(this);
92: this.collectionFactory = new CollectionFactory(this);
93: this.cacheManager = parent.getLiveObjectCache();
94: this.storage = acquireConnection();
95: this.queryFactory = new SparqlQueryFactory(this, storage);
96: this.mergeManager = new MergeManagerImpl(this);
97: this.changeManager = new ChangeManagerImpl(this);
98: this.useTransactionalOntology = true;
99: this.isActive = true;
100: }
101:
102: CloneBuilder getCloneBuilder() {
103: return cloneBuilder;
104: }
105:
106: /**
107: * This method returns null, since we don't support nested Units of Work yet.
108: */
109: @Override
110: public UnitOfWork acquireUnitOfWork() {
111: return null;
112: }
113:
114: @Override
115: protected ConnectionWrapper acquireConnection() {
116: final ConnectionWrapper conn = parent.acquireConnection();
117: conn.setUnitOfWork(this);
118: return conn;
119: }
120:
121: @Override
122: public <T> T readObject(Class<T> cls, Object primaryKey, Descriptor descriptor) {
123: Objects.requireNonNull(cls, ErrorUtils.getNPXMessageSupplier("cls"));
124: Objects.requireNonNull(primaryKey, ErrorUtils.getNPXMessageSupplier("primaryKey"));
125: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
126:
127: return readObjectInternal(cls, primaryKey, descriptor);
128: }
129:
130: private <T> T readObjectInternal(Class<T> cls, Object identifier, Descriptor descriptor) {
131: assert cls != null;
132: assert identifier != null;
133: assert descriptor != null;
134: // First try to find the object among new uncommitted objects
135: Object result = newObjectsKeyToClone.get(identifier);
136: if (result != null && (isInRepository(descriptor, result))) {
137: // The result can be returned, since it is already registered in this UOW
138: return cls.cast(result);
139: }
140: // Object is already managed
141: result = keysToClones.get(identifier);
142: if (result != null) {
143: if (!cls.isAssignableFrom(result.getClass())) {
144: throw individualAlreadyManaged(identifier);
145: }
146: if (isInRepository(descriptor, result) && !getDeletedObjects().containsKey(result)) {
147: return cls.cast(result);
148: }
149: }
150: final URI idUri = EntityPropertiesUtils.getValueAsURI(identifier);
151: result = storage.find(new LoadingParameters<>(cls, idUri, descriptor));
152:
153: if (result == null) {
154: return null;
155: }
156: final Object clone = registerExistingObject(result, descriptor,
157: Collections.singletonList(new PostLoadInvoker(getMetamodel())));
158: checkForCollections(clone);
159: return cls.cast(clone);
160: }
161:
162: private static OWLEntityExistsException individualAlreadyManaged(Object identifier) {
163: return new OWLEntityExistsException(
164: "An entity with URI " + identifier + " is already present in the current persistence context.");
165: }
166:
167: /**
168: * This method calculates the changes that were to the registered entities and adds these changes into the given
169: * change set for future commit to the ontology.
170: */
171: private void calculateChanges() {
172: if (hasNew) {
173: calculateNewObjects(uowChangeSet);
174: }
175: if (hasDeleted) {
176: calculateDeletedObjects(uowChangeSet);
177: }
178: }
179:
180: /**
181: * Create object change sets for the new objects and adds them into our UnitOfWorkChangeSet.
182: *
183: * @param changeSet UnitOfWorkChangeSet
184: */
185: private void calculateNewObjects(UnitOfWorkChangeSet changeSet) {
186: for (Object clone : getNewObjectsCloneToOriginal().keySet()) {
187: final Descriptor c = getDescriptor(clone);
188: Object original = getNewObjectsCloneToOriginal()
189: .computeIfAbsent(clone, key -> cloneBuilder.buildClone(key, new CloneConfiguration(c)));
190: if (original == null) {
191: throw new OWLPersistenceException(
192: "Error while calculating changes for new objects. Original not found.");
193: }
194: getNewObjectsCloneToOriginal().put(clone, original);
195: getNewObjectsOriginalToClone().put(original, clone);
196: changeSet.addNewObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
197: c));
198: }
199: }
200:
201: private void calculateDeletedObjects(final UnitOfWorkChangeSet changeSet) {
202: for (Object clone : getDeletedObjects().keySet()) {
203: Object original = cloneToOriginals.get(clone);
204: if (original == null) {
205: throw new OWLPersistenceException("Cannot find an original for clone!");
206: }
207: Descriptor descriptor = getDescriptor(clone);
208: changeSet.addDeletedObjectChangeSet(ChangeSetFactory.createObjectChangeSet(original, clone,
209: descriptor));
210: }
211: }
212:
213: @Override
214: public void clear() {
215: detachAllManagedInstances();
216: cloneToOriginals.clear();
217: keysToClones.clear();
218: this.deletedObjects = null;
219: this.newObjectsCloneToOriginal = null;
220: this.newObjectsOriginalToClone = null;
221: this.newObjectsKeyToClone.clear();
222: this.hasChanges = false;
223: this.hasDeleted = false;
224: this.hasNew = false;
225: cloneBuilder.reset();
226: this.repoMap = new RepositoryMap();
227: repoMap.initDescriptors();
228: this.uowChangeSet = ChangeSetFactory.createUoWChangeSet();
229: }
230:
231: private void detachAllManagedInstances() {
232: cloneMapping.forEach(instance -> {
233: removeIndirectCollections(instance);
234: deregisterEntityFromPersistenceContext(instance);
235: });
236: getNewObjectsCloneToOriginal().keySet().forEach(instance -> {
237: removeIndirectCollections(instance);
238: deregisterEntityFromPersistenceContext(instance);
239: });
240: }
241:
242: @Override
243: public boolean contains(Object entity) {
244: Objects.requireNonNull(entity);
245:
246: return isObjectManaged(entity);
247: }
248:
249: @Override
250: public void commit() {
251: LOG.trace("UnitOfWork commit started.");
252: if (!isActive()) {
253: throw new IllegalStateException("Cannot commit inactive Unit of Work!");
254: }
255: this.inCommit = true;
256: commitUnitOfWork();
257: LOG.trace("UnitOfWork commit finished.");
258: }
259:
260: @Override
261: public void rollback() {
262: LOG.trace("UnitOfWork rollback started.");
263: if (!isActive()) {
264: throw new IllegalStateException("Cannot rollback inactive Unit of Work!");
265: }
266: storage.rollback();
267: clear();
268: }
269:
270: /**
271: * Commit this Unit of Work.
272: */
273: private void commitUnitOfWork() {
274: commitToOntology();
275: mergeChangesIntoParent();
276: postCommit();
277: }
278:
279: /**
280: * Clean up after the commit.
281: */
282: private void postCommit() {
283: clear();
284: this.inCommit = false;
285: if (shouldClearCacheAfterCommit) {
286: cacheManager.evictAll();
287: this.shouldReleaseAfterCommit = true;
288: }
289: }
290:
291: /**
292: * If there are any changes, commit them to the ontology.
293: */
294: private void commitToOntology() {
295: if (this.hasNew || this.hasChanges || this.hasDeleted) {
296: calculateChanges();
297: }
298: validateIntegrityConstraints();
299: storageCommit();
300: }
301:
302: private void validateIntegrityConstraints() {
303: final IntegrityConstraintsValidator validator = IntegrityConstraintsValidator.getValidator();
304: for (ObjectChangeSet changeSet : uowChangeSet.getNewObjects()) {
305: validator.validate(changeSet.getCloneObject(),
306: entityType((Class<Object>) changeSet.getObjectClass()), false);
307: }
308: uowChangeSet.getExistingObjectsChanges().forEach(changeSet -> validator.validate(changeSet, getMetamodel()));
309: }
310:
311: private static Map<Object, Object> createMap() {
312: return new IdentityHashMap<>();
313: }
314:
315: /**
316: * Gets current state of the specified entity.
317: * <p>
318: * Note that since no repository is specified we can only determine if the entity is managed or removed. Therefore
319: * if the case is different this method returns State#NOT_MANAGED.
320: *
321: * @param entity The entity to check
322: * @return State of the entity
323: */
324: public State getState(Object entity) {
325: Objects.requireNonNull(entity);
326:
327: if (getDeletedObjects().containsKey(entity)) {
328: return State.REMOVED;
329: } else if (getNewObjectsCloneToOriginal().containsKey(entity)) {
330: return State.MANAGED_NEW;
331: } else if (cloneMapping.contains(entity)) {
332: return State.MANAGED;
333: } else {
334: return State.NOT_MANAGED;
335: }
336: }
337:
338: /**
339: * Checks the state of the specified entity with regards to the specified repository.
340: *
341: * @param entity Object
342: * @param descriptor Entity descriptor
343: * @return The state of the specified entity
344: */
345: public State getState(Object entity, Descriptor descriptor) {
346: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
347: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
348:
349: if (getDeletedObjects().containsKey(entity)) {
350: return State.REMOVED;
351: } else if (getNewObjectsCloneToOriginal().containsKey(entity) && isInRepository(descriptor, entity)) {
352: return State.MANAGED_NEW;
353: }
354: else if (cloneMapping.contains(entity) && isInRepository(descriptor, entity)) {
355: return State.MANAGED;
356: } else {
357: return State.NOT_MANAGED;
358: }
359: }
360:
361: /**
362: * Tries to find the original object for the given clone. It searches the existing objects, new objects and deleted
363: * objects.
364: *
365: * @param clone Object
366: * @return The original object for the given clone
367: */
368: public Object getOriginal(Object clone) {
369: if (clone == null) {
370: return null;
371: }
372: Object original = cloneToOriginals.get(clone);
373: if (original == null) {
374: original = getNewObjectsCloneToOriginal().get(clone);
375: }
376: return original;
377: }
378:
379: /**
380: * Gets managed original with the specified identifier or {@code null} if there is none matching.
381: * <p>
382: * Descriptor is used to check repository context validity.
383: *
384: * @param cls Return type of the original
385: * @param identifier Instance identifier
386: * @param descriptor Repository descriptor
387: * @return Original object managed by this UoW or {@code null} if this UoW doesn't contain a matching instance
388: */
389: public <T> T getManagedOriginal(Class<T> cls, Object identifier, Descriptor descriptor) {
390: if (!keysToClones.containsKey(identifier)) {
391: return null;
392: }
393: final Object clone = keysToClones.get(identifier);
394: if (!cls.isAssignableFrom(clone.getClass())) {
395: return null;
396: }
397: if (!isInRepository(descriptor, clone)) {
398: return null;
399: }
400: return cls.cast(cloneToOriginals.get(clone));
401: }
402:
403: /**
404: * Check if this UnitOfWork contains this original entity. This method is used by the CloneBuilder so it does not
405: * have to clone already managed referenced objects.
406: *
407: * @param entity The original entity.
408: * @return True if the original is managed in this UnitOfWork.
409: */
410: boolean containsOriginal(Object entity) {
411: return entity != null && cloneToOriginals.containsValue(entity);
412: }
413:
414: /**
415: * Finds clone of the specified original.
416: *
417: * @param original The original object whose clone we are looking for
418: * @return The clone or null, if there is none
419: */
420: public Object getCloneForOriginal(Object original) {
421: for (Entry<Object, Object> entry : cloneToOriginals.entrySet()) {
422: // We use IdentityMap, so we can use ==
423: if (entry.getValue() == original) {
424: return entry.getKey();
425: }
426: }
427: return null;
428: }
429:
430: public boolean hasChanges() {
431: return hasChanges || hasDeleted || hasNew;
432: }
433:
434: void setHasChanges() {
435: this.hasChanges = true;
436: }
437:
438: Map<Object, Object> getDeletedObjects() {
439: if (deletedObjects == null) {
440: this.deletedObjects = createMap();
441: }
442: return deletedObjects;
443: }
444:
445: Map<Object, Object> getNewObjectsCloneToOriginal() {
446: if (newObjectsCloneToOriginal == null) {
447: this.newObjectsCloneToOriginal = createMap();
448: }
449: return newObjectsCloneToOriginal;
450: }
451:
452: private Map<Object, Object> getNewObjectsOriginalToClone() {
453: if (newObjectsOriginalToClone == null) {
454: this.newObjectsOriginalToClone = createMap();
455: }
456: return newObjectsOriginalToClone;
457: }
458:
459: @Override
460: public CacheManager getLiveObjectCache() {
461: return parent.getLiveObjectCache();
462: }
463:
464: UnitOfWorkChangeSet getUowChangeSet() {
465: return uowChangeSet;
466: }
467:
468: @Override
469: public boolean isActive() {
470: return this.isActive;
471: }
472:
473: /**
474: * Returns true if the given clone represents a newly created object. Otherwise returns false.
475: *
476: * @param clone Object
477: * @return boolean
478: */
479: public boolean isObjectNew(Object clone) {
480: return clone != null && getNewObjectsCloneToOriginal().containsKey(clone);
481: }
482:
483: /**
484: * Returns true if the given object is already managed.
485: *
486: * @param entity Object
487: * @return boolean
488: */
489: @Override
490: public boolean isObjectManaged(Object entity) {
491: Objects.requireNonNull(entity);
492:
493: return cloneMapping.contains(entity) && !getDeletedObjects().containsKey(entity) || getNewObjectsCloneToOriginal().containsKey(entity);
494: }
495:
496: /**
497: * Persists changed value of the specified field.
498: *
499: * @param entity Entity with changes (the clone)
500: * @param f The field whose value has changed
501: * @throws IllegalStateException If this UoW is not in transaction
502: */
503: public void attributeChanged(Object entity, Field f) {
504: if (!isInTransaction()) {
505: throw new IllegalStateException("This unit of work is not in a transaction.");
506: }
507: final Descriptor descriptor = getDescriptor(entity);
508: if (descriptor == null) {
509: throw new OWLPersistenceException("Unable to find repository for entity " + entity
510: + ". Is it registered in this UoW?");
511: }
512: final EntityTypeImpl<?> et = entityType(entity.getClass());
513: et.getLifecycleListenerManager().invokePreUpdateCallbacks(entity);
514: storage.merge(entity, f, descriptor);
515: createChangeRecord(entity, et.getFieldSpecification(f.getName()), descriptor);
516: setHasChanges();
517: setIndirectCollectionIfPresent(entity, f);
518: et.getLifecycleListenerManager().invokePostUpdateCallbacks(entity);
519: }
520:
521: private void createChangeRecord(Object clone, FieldSpecification<?, ?> fieldSpec, Descriptor descriptor) {
522: final Object orig = getOriginal(clone);
523: if (orig == null) {
524: return;
525: }
526: final ChangeRecord record = new ChangeRecordImpl(fieldSpec,
527: EntityPropertiesUtils.getFieldValue(fieldSpec.getJavaField(), clone));
528: registerChangeRecord(clone, orig, descriptor, record);
529: }
530:
531: private void registerChangeRecord(Object clone, Object orig, Descriptor descriptor, ChangeRecord record) {
532: ObjectChangeSet chSet = uowChangeSet.getExistingObjectChanges(orig);
533: if (chSet == null) {
534: chSet = ChangeSetFactory.createObjectChangeSet(orig, clone, descriptor);
535: uowChangeSet.addObjectChangeSet(chSet);
536: }
537: chSet.addChangeRecord(record);
538: }
539:
540: /**
541: * Merge the changes from this Unit of Work's change set into the server session.
542: */
543: private void mergeChangesIntoParent() {
544: if (hasChanges()) {
545: mergeManager.mergeChangesFromChangeSet(uowChangeSet);
546: }
547: }
548:
549: @Override
550: public <T> T mergeDetached(T entity, Descriptor descriptor) {
551: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
552: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
553:
554: final Object id = getIdentifier(entity);
555: if (!storage.contains(id, entity.getClass(), descriptor)) {
556: registerNewObject(entity, descriptor);
557: return entity;
558: } else {
559: if (isIndividualManaged(id, entity) && !isSameType(id, entity)) {
560: throw individualAlreadyManaged(id);
561: }
562: return mergeDetachedInternal(entity, descriptor);
563: }
564: }
565:
566: private boolean isSameType(Object id, Object entity) {
567: final Class<?> mergedType = entity.getClass();
568: final Object managed = keysToClones.containsKey(id) ? keysToClones.get(id) : newObjectsKeyToClone.get(id);
569: return managed != null && managed.getClass().isAssignableFrom(mergedType);
570: }
571:
572: private <T> T mergeDetachedInternal(T entity, Descriptor descriptor) {
573: assert entity != null;
574: final EntityTypeImpl<T> et = (EntityTypeImpl<T>) entityType(entity.getClass());
575: final URI idUri = EntityPropertiesUtils.getIdentifier(entity, et);
576:
577: final Object clone = getInstanceForMerge(idUri, et, descriptor);
578: try {
579: // Merge only the changed attributes
580: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(clone, entity, descriptor);
581: LOG.debug("Clone: " + clone + ", merged entity: " + entity);
582: changeManager.calculateChanges(chSet);
583: if (chSet.hasChanges()) {
584: et.getLifecycleListenerManager().invokePreUpdateCallbacks(clone);
585: final DetachedInstanceMerger merger = new DetachedInstanceMerger(this);
586: merger.mergeChangesFromDetachedToManagedInstance(chSet, descriptor);
587: for (ChangeRecord record : chSet.getChanges()) {
588: final Field field = record.getAttribute().getJavaField();
589: storage.merge(clone, field, descriptor);
590: }
591: et.getLifecycleListenerManager().invokePostUpdateCallbacks(clone);
592: uowChangeSet.addObjectChangeSet(copyChangeSet(chSet, getOriginal(clone), clone, descriptor));
593: }
594: } catch (OWLEntityExistsException e) {
595: unregisterObject(clone);
596: throw e;
597: }
598: if (cacheManager.contains(et.getJavaType(), idUri, descriptor)) {
599: cacheManager.evict(et.getJavaType(), idUri, descriptor.getContext());
600: }
601: setHasChanges();
602: checkForCollections(clone);
603: return et.getJavaType().cast(clone);
604: }
605:
606: private <T> Object getInstanceForMerge(URI identifier, EntityType<T> et, Descriptor descriptor) {
607: if (keysToClones.containsKey(identifier)) {
608: return keysToClones.get(identifier);
609: }
610: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), identifier, descriptor, true);
611: T original = storage.find(params);
612: assert original != null;
613:
614: return registerExistingObject(original, descriptor);
615: }
616:
617: private ObjectChangeSet copyChangeSet(ObjectChangeSet changeSet, Object original, Object clone,
618: Descriptor descriptor) {
619: final ObjectChangeSet newChangeSet = ChangeSetFactory.createObjectChangeSet(original, clone, descriptor);
620: changeSet.getChanges().forEach(newChangeSet::addChangeRecord);
621: return newChangeSet;
622: }
623:
624: private void registerEntityWithPersistenceContext(Object entity) {
625: Aspects.aspectOf(BeanListenerAspect.class).register(entity, this);
626: }
627:
628: private void deregisterEntityFromPersistenceContext(Object entity) {
629: Aspects.aspectOf(BeanListenerAspect.class).deregister(entity);
630: }
631:
632: @Override
633: public NamedQueryManager getNamedQueryManager() {
634: return parent.getNamedQueryManager();
635: }
636:
637: @Override
638: public ResultSetMappingManager getResultSetMappingManager() {
639: return parent.getResultSetMappingManager();
640: }
641:
642: @Override
643: public Object registerExistingObject(Object entity, Descriptor descriptor) {
644: return registerExistingObject(entity, descriptor, Collections.emptyList());
645: }
646:
647: @Override
648: public Object registerExistingObject(Object entity, Descriptor descriptor, List<Consumer<Object>> postClone) {
649: if (entity == null) {
650: return null;
651: }
652: if (cloneToOriginals.containsValue(entity)) {
653: return getCloneForOriginal(entity);
654: }
655: final CloneConfiguration cloneConfig = new CloneConfiguration(descriptor);
656: postClone.forEach(cloneConfig::addPostRegisterHandler);
657: Object clone = cloneBuilder.buildClone(entity, cloneConfig);
658: assert clone != null;
659: registerClone(clone, entity, descriptor);
660: postClone.forEach(c -> c.accept(clone));
661: return clone;
662: }
663:
664: private void registerClone(Object clone, Object original, Descriptor descriptor) {
665: cloneToOriginals.put(clone, original);
666: final Object identifier = EntityPropertiesUtils.getIdentifier(clone, getMetamodel());
667: keysToClones.put(identifier, clone);
668: registerEntityWithPersistenceContext(clone);
669: registerEntityWithOntologyContext(descriptor, clone);
670: }
671:
672: /**
673: * Release this Unit of Work. Releasing an active Unit of Work with uncommitted changes causes all pending changes
674: * to be discarded.
675: */
676: @Override
677: public void release() {
678: clear();
679: storage.close();
680: this.isActive = false;
681: LOG.debug("UnitOfWork released.");
682: }
683:
684: @Override
685: public <T> void refreshObject(T object) {
686: Objects.requireNonNull(object);
687: if (!isObjectManaged(object)) {
688: throw new IllegalArgumentException(
689: "Cannot call refresh on an instance not managed by this persistence context.");
690: }
691: final EntityTypeImpl<T> et = entityType((Class<T>) object.getClass());
692: final URI idUri = EntityPropertiesUtils.getIdentifier(object, et);
693: final Descriptor descriptor = getDescriptor(object);
694: assert descriptor != null;
695:
696: final LoadingParameters<T> params = new LoadingParameters<>(et.getJavaType(), idUri, descriptor, true);
697: params.bypassCache();
698: final ConnectionWrapper connection = acquireConnection();
699: try {
700: uowChangeSet.cancelObjectChanges(getOriginal(object));
701: T original = connection.find(params);
702: if (original == null) {
703: throw new EntityNotFoundException("Entity " + object + " no longer exists in the repository.");
704: }
705: T source = (T) cloneBuilder.buildClone(original, new CloneConfiguration(descriptor));
706: final ObjectChangeSet chSet = ChangeSetFactory.createObjectChangeSet(source, object, descriptor);
707: changeManager.calculateChanges(chSet);
708: new RefreshInstanceMerger(collectionFactory).mergeChanges(chSet);
709: revertTransactionalChanges(object, descriptor, chSet);
710: registerClone(object, original, descriptor);
711: et.getLifecycleListenerManager().invokePostLoadCallbacks(object);
712: } finally {
713: connection.close();
714: }
715: }
716:
717: private <T> void revertTransactionalChanges(T object, Descriptor descriptor, ObjectChangeSet chSet) {
718: for (ChangeRecord change : chSet.getChanges()) {
719: storage.merge(object, change.getAttribute().getJavaField(),
720: descriptor.getAttributeDescriptor(change.getAttribute()));
721: }
722: }
723:
724: @Override
725: public void registerNewObject(Object entity, Descriptor descriptor) {
726: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
727: Objects.requireNonNull(descriptor, ErrorUtils.getNPXMessageSupplier("descriptor"));
728:
729: registerNewObjectInternal(entity, descriptor);
730: }
731:
732: /**
733: * Registers the specified entity for persist in this Unit of Work.
734: *
735: * @param entity The entity to register
736: * @param descriptor Entity descriptor, specifying optionally contexts into which the entity will be persisted
737: */
738: private void registerNewObjectInternal(Object entity, Descriptor descriptor) {
739: final EntityTypeImpl<?> eType = entityType(entity.getClass());
740: eType.getLifecycleListenerManager().invokePrePersistCallbacks(entity);
741: Object id = getIdentifier(entity);
742: if (id == null) {
743: EntityPropertiesUtils.verifyIdentifierIsGenerated(entity, eType);
744: }
745: verifyCanPersist(id, entity, eType, descriptor);
746: storage.persist(id, entity, descriptor);
747: if (id == null) {
748: // If the ID was null, extract it from the entity. It is present now
749: id = getIdentifier(entity);
750: }
751: assert id != null;
752: // Original is null until commit
753: // cloneMapping.put(entity, entity);
754: getNewObjectsCloneToOriginal().put(entity, null);
755: registerEntityWithPersistenceContext(entity);
756: registerEntityWithOntologyContext(descriptor, entity);
757: newObjectsKeyToClone.put(id, entity);
758: checkForCollections(entity);
759: this.hasNew = true;
760: eType.getLifecycleListenerManager().invokePostPersistCallbacks(entity);
761: }
762:
763: private void verifyCanPersist(Object id, Object instance, EntityType<?> et, Descriptor descriptor) {
764: if (isIndividualManaged(id, instance) && !instance.getClass().isEnum()) {
765: throw individualAlreadyManaged(id);
766: }
767: if (storage.contains(id, instance.getClass(), descriptor)) {
768: throw new OWLEntityExistsException(
769: "Individual " + id + " of type " + et.getIRI() + " already exists in storage.");
770: }
771: }
772:
773: private boolean isIndividualManaged(Object identifier, Object entity) {
774: // Allows persisting the same individual into different contexts
775: return keysToClones.containsKey(identifier) ||
776: newObjectsKeyToClone.containsKey(identifier) && !cloneMapping.contains(entity);
777: }
778:
779: @Override
780: public void removeObject(Object entity) {
781: assert entity != null;
782: if (!isObjectManaged(entity)) {
783: throw new IllegalArgumentException(
784: "Cannot remove entity which is not managed in the current persistence context.");
785: }
786: final EntityTypeImpl<?> et = entityType(entity.getClass());
787: et.getLifecycleListenerManager().invokePreRemoveCallbacks(entity);
788: final Object primaryKey = getIdentifier(entity);
789: final Descriptor descriptor = getDescriptor(entity);
790:
791: if (hasNew && getNewObjectsCloneToOriginal().containsKey(entity)) {
792: unregisterObject(entity);
793: newObjectsKeyToClone.remove(primaryKey);
794: } else {
795: getDeletedObjects().put(entity, entity);
796: this.hasDeleted = true;
797: }
798: storage.remove(primaryKey, et.getJavaType(), descriptor);
799: et.getLifecycleListenerManager().invokePostRemoveCallbacks(entity);
800: }
801:
802: @Override
803: public void restoreRemovedObject(Object entity) {
804: assert deletedObjects.containsKey(entity);
805:
806: deletedObjects.remove(entity);
807: final Object id = getIdentifier(entity);
808: storage.persist(id, entity, getDescriptor(entity));
809:
810: }
811:
812: /**
813: * Remove the registered object from this Unit of Work.
814: *
815: * @param object Clone of the original object
816: */
817: public void unregisterObject(Object object) {
818: if (object == null) {
819: return;
820: }
821: final Object original = cloneToOriginals.remove(object);
822: keysToClones.remove(EntityPropertiesUtils.getIdentifier(object, getMetamodel()));
823:
824: getDeletedObjects().remove(object);
825: if (hasNew) {
826: Object newOriginal = getNewObjectsCloneToOriginal().remove(object);
827: if (newOriginal != null) {
828: getNewObjectsOriginalToClone().remove(newOriginal);
829: }
830: }
831: if (original != null) {
832: cloneBuilder.removeVisited(original, repoMap.getEntityDescriptor(object));
833: }
834: unregisterObjectFromPersistenceContext(object);
835: }
836:
837: private void unregisterObjectFromPersistenceContext(Object object) {
838: removeIndirectCollections(object);
839: deregisterEntityFromPersistenceContext(object);
840: unregisterEntityFromOntologyContext(object);
841: }
842:
843: @Override
844: public boolean shouldReleaseAfterCommit() {
845: return shouldReleaseAfterCommit;
846: }
847:
848: public void setShouldClearAfterCommit(boolean shouldClearCache) {
849: this.shouldClearCacheAfterCommit = shouldClearCache;
850: }
851:
852: public void setEntityManager(AbstractEntityManager entityManager) {
853: this.entityManager = entityManager;
854: }
855:
856: @Override
857: public void writeUncommittedChanges() {
858: if (!hasChanges()) {
859: return;
860: }
861: commitUnitOfWork();
862: }
863:
864: @Override
865: public MetamodelImpl getMetamodel() {
866: return parent.getMetamodel();
867: }
868:
869: private <T> EntityTypeImpl<T> entityType(Class<T> cls) {
870: return getMetamodel().entity(cls);
871: }
872:
873: @Override
874: public boolean isEntityType(Class<?> cls) {
875: return parent.isEntityType(cls);
876: }
877:
878: @Override
879: public boolean isInTransaction() {
880: return entityManager != null && entityManager.getTransaction().isActive();
881: }
882:
883: /**
884: * Returns {@code true} if this UoW is currently committing changes.
885: *
886: * @return Whether this UoW is in the commit phase
887: */
888: public boolean isInCommit() {
889: return inCommit;
890: }
891:
892: @Override
893: public <T> void loadEntityField(T entity, Field field) {
894: Objects.requireNonNull(entity, ErrorUtils.getNPXMessageSupplier("entity"));
895: Objects.requireNonNull(field, ErrorUtils.getNPXMessageSupplier("field"));
896:
897: if (EntityPropertiesUtils.getFieldValue(field, entity) != null) {
898: return;
899: }
900: final Descriptor entityDescriptor = getDescriptor(entity);
901: if (entityDescriptor == null) {
902: throw new OWLPersistenceException(
903: "Unable to find repository identifier for entity " + entity + ". Is it managed by this UoW?");
904: }
905: storage.loadFieldValue(entity, field, entityDescriptor);
906: final Object orig = EntityPropertiesUtils.getFieldValue(field, entity);
907: final Object entityOriginal = getOriginal(entity);
908: if (entityOriginal != null) {
909: EntityPropertiesUtils.setFieldValue(field, entityOriginal, orig);
910: }
911: final Descriptor fieldDescriptor = getFieldDescriptor(entity, field, entityDescriptor);
912: final Object clone = cloneLoadedFieldValue(entity, field, fieldDescriptor, orig);
913: EntityPropertiesUtils.setFieldValue(field, entity, clone);
914: }
915:
916: private <T> Descriptor getFieldDescriptor(T entity, Field field, Descriptor entityDescriptor) {
917: final EntityType<?> et = entityType(entity.getClass());
918: final FieldSpecification<?, ?> fieldSpec = et
919: .getFieldSpecification(field.getName());
920: return entityDescriptor.getAttributeDescriptor(fieldSpec);
921: }
922:
923: private <T> Object cloneLoadedFieldValue(T entity, Field field, final Descriptor fieldDescriptor,
924: final Object fieldValueOrig) {
925: Object clone;
926: if (fieldValueOrig == null) {
927: clone = null;
928: } else {
929: if (isEntityType(field.getType())) {
930: clone = registerExistingObject(fieldValueOrig, fieldDescriptor);
931: putObjectIntoCache(getIdentifier(clone), fieldValueOrig, fieldDescriptor);
932: } else {
933: clone = cloneBuilder.buildClone(entity, field, fieldValueOrig, fieldDescriptor);
934: }
935: }
936: return clone;
937: }
938:
939: @Override
940: public void removeObjectFromCache(Object toRemove, URI context) {
941: Objects.requireNonNull(toRemove, ErrorUtils.getNPXMessageSupplier("toRemove"));
942:
943: final Object primaryKey = getIdentifier(toRemove);
944: cacheManager.evict(toRemove.getClass(), primaryKey, context);
945: }
946:
947: @Override
948: public boolean isConsistent(URI context) {
949: return storage.isConsistent(context);
950: }
951:
952: @Override
953: public List<URI> getContexts() {
954: return storage.getContexts();
955: }
956:
957: @Override
958: public void setUseTransactionalOntologyForQueryProcessing() {
959: this.useTransactionalOntology = true;
960: }
961:
962: @Override
963: public boolean useTransactionalOntologyForQueryProcessing() {
964: return useTransactionalOntology;
965: }
966:
967: @Override
968: public void setUseBackupOntologyForQueryProcessing() {
969: this.useTransactionalOntology = false;
970: }
971:
972: @Override
973: public boolean useBackupOntologyForQueryProcessing() {
974: return !useTransactionalOntology;
975: }
976:
977: @Override
978: public QueryImpl createNativeQuery(String sparql) {
979: return queryFactory.createNativeQuery(sparql);
980: }
981:
982: @Override
983: public <T> TypedQueryImpl<T> createNativeQuery(String sparql, Class<T> resultClass) {
984: return queryFactory.createNativeQuery(sparql, resultClass);
985: }
986:
987: @Override
988: public QueryImpl createNativeQuery(String sparql, String resultSetMapping) {
989: return queryFactory.createNativeQuery(sparql, resultSetMapping);
990: }
991:
992: @Override
993: public QueryImpl createQuery(String query) {
994: return queryFactory.createQuery(query);
995: }
996:
997: @Override
998: public <T> TypedQueryImpl<T> createQuery(String query, Class<T> resultClass) {
999: return queryFactory.createQuery(query, resultClass);
1000: }
1001:
1002: @Override
1003: public QueryImpl createNamedQuery(String name) {
1004: return queryFactory.createNamedQuery(name);
1005: }
1006:
1007: @Override
1008: public <T> TypedQueryImpl<T> createNamedQuery(String name, Class<T> resultClass) {
1009: return queryFactory.createNamedQuery(name, resultClass);
1010: }
1011:
1012: /**
1013: * Check if the specified entity contains a collection. If so, replace it with its indirect representation so that
1014: * changes in that collection can be tracked.
1015: *
1016: * @param entity The entity to check
1017: */
1018: private void checkForCollections(Object entity) {
1019: assert entity != null;
1020: final EntityType<?> et = entityType(entity.getClass());
1021: for (FieldSpecification<?, ?> fieldSpec : et.getFieldSpecifications()) {
1022: setIndirectCollectionIfPresent(entity, fieldSpec.getJavaField());
1023: }
1024: }
1025:
1026: /**
1027: * Create and set indirect collection on the specified entity field.
1028: * <p>
1029: * If the specified field is of Collection type and it is not already an indirect collection, create new one and set
1030: * it as the value of the specified field on the specified entity.
1031: *
1032: * @param entity The entity collection will be set on
1033: * @param field The field to set
1034: * @throws IllegalArgumentException Reflection
1035: */
1036: private void setIndirectCollectionIfPresent(Object entity, Field field) {
1037:• assert entity != null;
1038:• assert field != null;
1039:
1040: final Object value = EntityPropertiesUtils.getFieldValue(field, entity);
1041:• if (value instanceof IndirectCollection) {
1042: return;
1043: }
1044:• if (value instanceof Collection || value instanceof Map) {
1045: EntityPropertiesUtils.setFieldValue(field, entity, createIndirectCollection(value, entity, field));
1046: }
1047: }
1048:
1049: /**
1050: * Creates an indirect collection, which wraps the specified collection instance and propagates changes to the
1051: * persistence context.
1052: *
1053: * @param collection Collection to be proxied
1054: * @param owner Collection owner instance
1055: * @param field Field filled with the collection
1056: * @return Indirect collection
1057: */
1058: public IndirectCollection<?> createIndirectCollection(Object collection, Object owner, Field field) {
1059: return collectionFactory.createIndirectCollection(collection, owner, field);
1060: }
1061:
1062: /**
1063: * Remove indirect collection implementations from the specified entity (if present).
1064: *
1065: * @param entity The entity to remove indirect collections from
1066: */
1067: private void removeIndirectCollections(Object entity) {
1068: Field[] fields = entity.getClass().getDeclaredFields();
1069: for (Field f : fields) {
1070: final Object ob = EntityPropertiesUtils.getFieldValue(f, entity);
1071: if (ob instanceof IndirectCollection) {
1072: IndirectCollection<?> indCol = (IndirectCollection<?>) ob;
1073: EntityPropertiesUtils.setFieldValue(f, entity, indCol.getReferencedCollection());
1074: }
1075: }
1076: }
1077:
1078: void putObjectIntoCache(Object identifier, Object entity, Descriptor descriptor) {
1079: cacheManager.add(identifier, entity, descriptor);
1080: }
1081:
1082: private Object getIdentifier(Object entity) {
1083: assert entity != null;
1084: return EntityPropertiesUtils.getIdentifier(entity, getMetamodel());
1085: }
1086:
1087: private void unregisterEntityFromOntologyContext(Object entity) {
1088: assert entity != null;
1089:
1090: final Descriptor descriptor = repoMap.getEntityDescriptor(entity);
1091: if (descriptor == null) {
1092: throw new OWLPersistenceException("Fatal error, unable to find descriptor for entity " + entity);
1093: }
1094:
1095: repoMap.remove(descriptor, entity);
1096: repoMap.removeEntityToRepository(entity);
1097: }
1098:
1099: private void registerEntityWithOntologyContext(Descriptor repository, Object entity) {
1100: assert repository != null;
1101: assert entity != null;
1102:
1103: repoMap.add(repository, entity, null);
1104: repoMap.addEntityToRepository(entity, repository);
1105: }
1106:
1107: private boolean isInRepository(Descriptor descriptor, Object entity) {
1108: assert descriptor != null;
1109: assert entity != null;
1110:
1111: return repoMap.contains(descriptor, entity);
1112: }
1113:
1114: private Descriptor getDescriptor(Object entity) {
1115: assert entity != null;
1116:
1117: return repoMap.getEntityDescriptor(entity);
1118: }
1119:
1120: private void storageCommit() {
1121: try {
1122: storage.commit();
1123: } catch (OWLPersistenceException e) {
1124: entityManager.removeCurrentPersistenceContext();
1125: throw e;
1126: }
1127: }
1128:
1129: @Override
1130: public <T> T unwrap(Class<T> cls) {
1131: if (cls.isAssignableFrom(getClass())) {
1132: return cls.cast(this);
1133: }
1134: return storage.unwrap(cls);
1135: }
1136: }